#include <rtthread.h>
#include <stdbool.h>
#include <stdint.h>
#include "ch32f20x.h"
#include "dap_if_config.h"
#include "huart.h"

#define USE_USARTX                USART2
#define DMA_CHANNEL_TX            DMA1_Channel7
#define DMA_CHANNEL_RX            DMA1_Channel6
#define DMA_CHANNEL_TX_IRQN       DMA1_Channel7_IRQn
#define DMA_CHANNEL_RX_IRQN       DMA1_Channel6_IRQn
#define DMA_TX_IT_TC              DMA1_IT_TC7
#define DMA_RX_IT_TC              DMA1_IT_TC6
#define DMA_CHANNEL_TX_IRQHANDLER DMA1_Channel7_IRQHandler
#define DMA_CHANNEL_RX_IRQHANDLER DMA1_Channel6_IRQHandler

#define TIM_FQ     1000000
#define TIM_PERIOD 500
// #define TIM_FQ     10000
// #define TIM_PERIOD 1000

#define DMA_RX_PACKET_SIZE (CDC_PACKET_SIZE)

typedef struct
{
    DMA_InitTypeDef dma_tx_init;
    DMA_InitTypeDef dma_rx_init;

    rt_uint8_t *send_data;
    rt_uint8_t *recv_data;

    rt_uint16_t send_len;
    rt_uint16_t recv_len;

    rt_uint32_t recv_timeout_ticks;
} uart_handle_t;

static uart_handle_t HUART = {0};

int uart_init(void)
{
    // 开启外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

    // RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

    /*************************************************************************/
    // USART引脚配置
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // USART参数配置
    uart_configure(CONFIG_CDC_DEFAULT_BAUDRATE, CONFIG_CDC_DEFAULT_CHARFORMAT, CONFIG_CDC_DEFAULT_PARITYTYPE,
                   CONFIG_CDC_DEFAULT_DATABIT);

    /*************************************************************************/
    DMA_DeInit(DMA_CHANNEL_TX);
    DMA_DeInit(DMA_CHANNEL_RX);

    HUART.dma_tx_init.DMA_PeripheralBaseAddr = (rt_uint32_t)(&USE_USARTX->DATAR);
    HUART.dma_tx_init.DMA_MemoryBaseAddr = (rt_uint32_t)HUART.send_data;
    HUART.dma_tx_init.DMA_DIR = DMA_DIR_PeripheralDST;
    HUART.dma_tx_init.DMA_BufferSize = 0;
    HUART.dma_tx_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    HUART.dma_tx_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
    HUART.dma_tx_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    HUART.dma_tx_init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    HUART.dma_tx_init.DMA_Mode = DMA_Mode_Normal;
    HUART.dma_tx_init.DMA_Priority = DMA_Priority_VeryHigh;
    HUART.dma_tx_init.DMA_M2M = DMA_M2M_Disable;

    HUART.dma_rx_init.DMA_PeripheralBaseAddr = (rt_uint32_t)(&USE_USARTX->DATAR);
    HUART.dma_rx_init.DMA_MemoryBaseAddr = (rt_uint32_t)HUART.recv_data;
    HUART.dma_rx_init.DMA_DIR = DMA_DIR_PeripheralSRC;
    HUART.dma_rx_init.DMA_BufferSize = DMA_RX_PACKET_SIZE;
    HUART.dma_rx_init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    HUART.dma_rx_init.DMA_MemoryInc = DMA_MemoryInc_Enable;
    HUART.dma_rx_init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    HUART.dma_rx_init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    HUART.dma_rx_init.DMA_Mode = DMA_Mode_Normal;
    HUART.dma_rx_init.DMA_Priority = DMA_Priority_VeryHigh;
    HUART.dma_rx_init.DMA_M2M = DMA_M2M_Disable;

    DMA_Init(DMA_CHANNEL_TX, &HUART.dma_tx_init);
    DMA_Init(DMA_CHANNEL_RX, &HUART.dma_rx_init);

    // DMA中断
    DMA_ITConfig(DMA_CHANNEL_TX, DMA_IT_TC, ENABLE);
    DMA_ITConfig(DMA_CHANNEL_RX, DMA_IT_TC, ENABLE);

    /*************************************************************************/
    // 串口接收超时定时器
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};

    // 计时器时钟频率 = 分配器输入频率 / (PSC + 1)
    // 目前分配器输入频率 = APB1时钟频率
    TIM_TimeBaseInitStructure.TIM_Prescaler = (144000000 / TIM_FQ);
    TIM_TimeBaseInitStructure.TIM_Period = TIM_PERIOD;

    TIM6->CTLR1 |= 0x04; // 计时器上溢或下益才触发中断
    TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStructure);

    TIM_ARRPreloadConfig(TIM6, ENABLE);
    TIM_SelectOnePulseMode(TIM6, TIM_OPMode_Single);
    TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);

    /*************************************************************************/
    // NVIC中断
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_InitStructure.NVIC_IRQChannel = DMA_CHANNEL_TX_IRQN;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = DMA_CHANNEL_RX_IRQN;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStructure);

    USART_Cmd(USE_USARTX, ENABLE);
    return 0;
}

int uart_deinit(void)
{
    TIM_Cmd(TIM6, DISABLE);
    USART_Cmd(USE_USARTX, DISABLE);
    return 0;
}

int uart_configure(rt_uint32_t baudrate, rt_uint8_t stop_bit, rt_uint8_t parity_type, rt_uint8_t data_bits)
{
    // rt_kprintf("uart_configure  baudrate: %d, stop_bit: %d, parity_type: %d, data_bits: %d\n", baudrate, stop_bit,
    //            parity_type, data_bits);

    USART_InitTypeDef USART_InitStructure = {0};

    USART_InitStructure.USART_BaudRate = baudrate;

    // uart接收超时设置
    HUART.recv_timeout_ticks = (TIM_FQ) / (baudrate / 12);
    HUART.recv_timeout_ticks /= TIM_PERIOD;
    if (HUART.recv_timeout_ticks == 0)
    {
        HUART.recv_timeout_ticks = 1;
    }

    // 仅支持8或9个数据位
    if (data_bits == 8)
    {
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    }
    else
    {
        USART_InitStructure.USART_WordLength = USART_WordLength_9b;
    }

    switch (stop_bit)
    {
    case 0:
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        break;
    case 1:
        USART_InitStructure.USART_StopBits = USART_StopBits_1_5;
        break;
    case 2:
        USART_InitStructure.USART_StopBits = USART_StopBits_2;
        break;
    default:
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        break;
    }

    switch (parity_type)
    {
    case 0:
        USART_InitStructure.USART_Parity = USART_Parity_No;
        break;
    case 1:
        USART_InitStructure.USART_Parity = USART_Parity_Odd;
        break;
    case 2:
        USART_InitStructure.USART_Parity = USART_Parity_Even;
        break;
    default:
        USART_InitStructure.USART_Parity = USART_Parity_No;
        break;
    }

    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

    USART_Init(USE_USARTX, &USART_InitStructure);

    return 0;
}

int uart_send(rt_uint8_t *data, rt_uint16_t len)
{
    if (data == NULL)
    {
        return 1;
    }

    if (len == 0)
    {
        return 2;
    }

    DMA_Cmd(DMA_CHANNEL_TX, DISABLE);
    USART_DMACmd(USE_USARTX, USART_DMAReq_Tx, DISABLE);

    HUART.send_data = data;
    HUART.send_len = len;

    DMA_CHANNEL_TX->MADDR = (rt_uint32_t)data;
    DMA_CHANNEL_TX->CNTR = len;

    DMA_Cmd(DMA_CHANNEL_TX, ENABLE);
    USART_DMACmd(USE_USARTX, USART_DMAReq_Tx, ENABLE);

    return 0;
}

int uart_recv(rt_uint8_t *data, rt_uint16_t len)
{
    if (data == NULL)
    {
        return 1;
    }

    if (len == 0)
    {
        return 2;
    }

    DMA_Cmd(DMA_CHANNEL_RX, DISABLE);
    USART_DMACmd(USE_USARTX, USART_DMAReq_Rx, DISABLE);

    HUART.recv_data = data;
    HUART.recv_len = len;

    DMA_CHANNEL_RX->MADDR = (rt_uint32_t)data;
    DMA_CHANNEL_RX->CNTR = len;

    DMA_Cmd(DMA_CHANNEL_RX, ENABLE);
    USART_DMACmd(USE_USARTX, USART_DMAReq_Rx, ENABLE);

    TIM6->ATRLR = TIM_PERIOD;
    TIM_Cmd(TIM6, ENABLE);

    return 0;
}

void DMA_CHANNEL_TX_IRQHANDLER(void)
{
    rt_interrupt_enter();
    // rt_kprintf("DMA_CHANNEL_TX_IRQHANDLER\n");

    if (DMA_GetITStatus(DMA_TX_IT_TC))
    {
        DMA_ClearITPendingBit(DMA_TX_IT_TC);

        uart_send_callback(HUART.send_data, HUART.send_len);
    }

    rt_interrupt_leave();
}

void DMA_CHANNEL_RX_IRQHANDLER(void)
{
    rt_interrupt_enter();

    // rt_kprintf("DMA_CHANNEL_TX_IRQHANDLER\n");
    if (DMA_GetITStatus(DMA_RX_IT_TC))
    {
        DMA_ClearITPendingBit(DMA_RX_IT_TC);

        uart_recv_callback(HUART.recv_data, HUART.recv_len);

        HUART.recv_len = 0;
    }

    rt_interrupt_leave();
}

void TIM6_IRQHandler(void)
{
    rt_interrupt_enter();

    static rt_uint32_t dma_cntr_prev = 0;
    static rt_uint32_t timeout_ticks_count = 0;

    // rt_kprintf("TIM6_IRQHandler\n");
    if (TIM_GetFlagStatus(TIM6, TIM_FLAG_Update))
    {
        TIM_ClearITPendingBit(TIM6, TIM_IT_Update);

        // rt_kprintf("PADDR: %08X, MADDR: %08X, UART RX SIZE: %d\n", DMA_CHANNEL_RX->PADDR, DMA_CHANNEL_RX->MADDR,
        //            DMA_CHANNEL_RX->CNTR);

        if (DMA_CHANNEL_RX->CNTR != HUART.recv_len)
        {
            if (dma_cntr_prev != DMA_CHANNEL_RX->CNTR)
            {
                timeout_ticks_count = 0;
                dma_cntr_prev = DMA_CHANNEL_RX->CNTR;
            }
            else
            {
                timeout_ticks_count += 1;
                if (timeout_ticks_count > HUART.recv_timeout_ticks)
                {
                    uart_recv_callback(HUART.recv_data, HUART.recv_len - DMA_CHANNEL_RX->CNTR);
                    HUART.recv_len = 0;
                }
            }
        }

        if (HUART.recv_len)
        {
            TIM6->ATRLR = TIM_PERIOD;
            TIM_Cmd(TIM6, ENABLE);
        }
    }

    rt_interrupt_leave();
}
